package jbitarrays;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;

import jbitarrays.api.CrawlerReader;
import jbitarrays.api.CrawlerWriter;
import jbitarrays.api.InfiniteBitArray;

public class RunLengthEncodingWriter implements CrawlerWriter {
	private final int bitsForNumber;
	/**
	 * the biggest positive integer we can fit in {@link #bitsForNumber} bits
	 */
	private final int maxNumber;
	private final CrawlerWriter decorated;

	private boolean writingOnes;
	private int writtenSoFar;

	public RunLengthEncodingWriter(@Nonnull CrawlerWriter decorated, @Nonnegative int bitsForNumber) {
		this(decorated, bitsForNumber,/*readingOnes*/true, 0);
	}

	private RunLengthEncodingWriter(@Nonnull CrawlerWriter decorated, @Nonnegative int bitsForNumber, boolean writingOnes, int writtenSoFar) {
		if (decorated == null || bitsForNumber <= 0 || bitsForNumber > Integer.SIZE) {
			throw new IllegalArgumentException("decorated != null && Integer.SIZE >= bitsForNumber > 0");
		}
		this.decorated = decorated;
		this.bitsForNumber = bitsForNumber;
		this.writingOnes = writingOnes;
		int maxNumber = 0;
		for (int i = 0; i < Math.min(bitsForNumber, Integer.SIZE - 1); ++i) { // leave sign bit 0
			maxNumber |= 1 << i;
		}
		this.maxNumber = maxNumber;
	}

	private void writeNextIfRequired() {
		if (writtenSoFar == maxNumber) {
			flip();
		}
	}

	private void flip() {
		writingOnes = !writingOnes;
		decorated.setNextN(bitsForNumber, writtenSoFar); // advance it
		writtenSoFar = 0;
	}

	public void setNext(boolean value) {
		writeNextIfRequired();
		if (value != writingOnes) {
			flip();
		}
		decorated.clone().setNextN(bitsForNumber, ++writtenSoFar); // flush count now
	}

	public void setNextN(int howMany, int value) {
		Utils.delegateSetNextNtoSetNext(this, howMany, value);
	}

	/**
	 * If you try to read a number from an empty {@link InfiniteBitArray} it
	 * will loop indefinitely - best to throw here to discourage this
	 */
	public CrawlerReader toReader() throws UnsupportedOperationException {
		throw new UnsupportedOperationException();
	}

	@Override public CrawlerWriter clone() {
		return new RunLengthEncodingWriter(decorated.clone(), bitsForNumber, writingOnes, writtenSoFar);
	}
}
